﻿using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.IO;
using DirectShowLib;
using System.Collections.Generic;
using Shell32;
using DirectShowLib.Utils;
using MediaFoundation.EVR;

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
    public sealed class ExtensionAttribute : Attribute { }
}

namespace MediaPlayer
{
    public static class AspectRatio
    {
        public const double AROriginal = -1;
        public const double AR1_1 = 1.0;
        public const double AR5_4 = 1.25;
        public const double AR4_3 = 1.333333;
        public const double AR16_10 = 1.6;
        public const double AR16_9 = 1.777777;
        public const double AR221_1 = 2.21;
        public const double AR235_1 = 2.35;
        public const double AR239_1 = 2.39;
    }

    public enum PlayState
    {
        NoFile,
        Ready,
        Playing,
        Paused,
        Stopped
    };

    public enum MediaType
    {
        None,
        Audio,
        AudioVideo
    }

    public enum RenderMode
    {
        VMR7,
        VMR9,
        EVR
    }

    public enum FileDetails
    {
        Type = 9,
        Kind = 11,
        Length = 27
    }

    [ComVisible(false)]
    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000001-0000-0000-C000-000000000046")]
    internal interface IClassFactory
    {
        void CreateInstance([MarshalAs(UnmanagedType.Interface)] object pUnkOuter, ref Guid refiid, [MarshalAs(UnmanagedType.Interface)] out object ppunk);
        void LockServer(bool fLock);
    }

    public class Tuple<T, U>
    {
        public T First { get; private set; }
        public U Second { get; private set; }

        public Tuple(T first, U second)
        {
            this.First = first;
            this.Second = second;
        }
    }

    public static class Tuple
    {
        // The following method is declared to take advantage of
        // compiler type inference features and let us not specify
        // the type parameters manually.
        public static Tuple<T, U> Create<T, U>(T first, U second)
        {
            return new Tuple<T, U>(first, second);
        }
    }

    internal static class Extentions
    {
        public static KeyValuePair<string, string> GetDetail(this FileInfo fi, FileDetails detail)
        {
            return GetDetail(fi, (int)detail);
        }

        public static KeyValuePair<string, string> GetDetail(this FileInfo fi, int detail)
        {
            Shell shell = (Shell)Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
            //Shell shell = new ShellClass();

            Folder folder = shell.NameSpace(fi.DirectoryName);
            FolderItem item = folder.ParseName(fi.Name);

            string dtlDesc = folder.GetDetailsOf(null, detail);
            string dtlVal = folder.GetDetailsOf(item, detail);

            Marshal.ReleaseComObject(folder);
            Marshal.ReleaseComObject(shell);

            return new KeyValuePair<string, string>(dtlDesc, dtlVal);
        }

        public static Dictionary<string, string> GetDetails(this FileInfo fi)
        {
            Dictionary<string, string> ret = new Dictionary<string, string>();

            Shell shell = (Shell)Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
            //Shell shell = new ShellClass();

            Folder folder = shell.NameSpace(fi.DirectoryName);
            FolderItem item = folder.ParseName(fi.Name);

            for (int i = 0; i < short.MaxValue; i++)
            {
                string dtlDesc = folder.GetDetailsOf(null, i);
                string dtlVal = folder.GetDetailsOf(item, i);

                if (dtlVal == null || dtlVal == "")
                    continue;

                ret.Add(dtlDesc, dtlVal);
            }

            Marshal.ReleaseComObject(folder);
            Marshal.ReleaseComObject(shell);

            return ret;
        }
    }

    internal static class NativeMethods
    {
        [DllImport("kernel32.dll")]
        internal static extern IntPtr LoadLibrary(string filename);

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool FreeLibrary(IntPtr module);

        [DllImport("kernel32.dll")]
        internal static extern IntPtr GetProcAddress(IntPtr module, string procName);

        internal delegate int DllGetClassObjectDelegate(ref Guid clsid, ref Guid iid, out IntPtr obj);

        internal static IBaseFilter GetObject(string file, Type type)
        {
            IntPtr hModule = NativeMethods.LoadLibrary(file);
            if (hModule == IntPtr.Zero)
                throw new FileNotFoundException();

            object retval = null;

            IntPtr DllGetClassObjectPtr = NativeMethods.GetProcAddress(hModule, "DllGetClassObject");

            DllGetClassObjectDelegate getClassObject = (DllGetClassObjectDelegate)Marshal.GetDelegateForFunctionPointer(DllGetClassObjectPtr, typeof(DllGetClassObjectDelegate));

            Guid clsid = type.GUID;
            Guid iid = new Guid("{00000001-0000-0000-C000-000000000046}"); //IID_IClassFactory
            IntPtr objPtr;
            IClassFactory factory;

            int hr = getClassObject(ref clsid, ref iid, out objPtr);

            if ((hr == 0) && (objPtr != IntPtr.Zero))
                retval = Marshal.GetObjectForIUnknown(objPtr);

            factory = retval as IClassFactory;

            if (factory == null)
            {
                if (objPtr != null) Marshal.ReleaseComObject(objPtr);
                throw new Exception("Could not QueryInterface for the IClassFactory interface");
            }

            Guid baseFilterGUID = typeof(IBaseFilter).GUID;
            object oFilter;
            factory.CreateInstance(null, ref baseFilterGUID, out oFilter);

            IBaseFilter filter = oFilter as IBaseFilter;
            if (filter == null)
            {
                if (oFilter != null) Marshal.ReleaseComObject(oFilter);
                throw new Exception("Could not QueryInterface for the IBaseFilter interface");
            }


            return filter;
        }

        private delegate int DllGetClassObject(ref Guid classId, ref Guid interfaceId, [Out, MarshalAs(UnmanagedType.Interface)] out object ppunk);

        internal static IClassFactory GetClassFactory(string dllName, Guid filterPersistClass)
        {
            IntPtr dllHandle = NativeMethods.LoadLibrary(dllName);
            if (dllHandle == IntPtr.Zero) return null;

            // Keep a reference to the dll until the process\AppDomain dies.
            //DLL_LIST.AddDllHandle(dllHandle);

            //Get a pointer to the DllGetClassObject function
            IntPtr dllGetClassObjectPtr = NativeMethods.GetProcAddress(dllHandle, "DllGetClassObject");
            if (dllGetClassObjectPtr == IntPtr.Zero)
                return null;

            // Convert the function pointer to a .net delegate.
            DllGetClassObject dllGetClassObject = (DllGetClassObject)Marshal.GetDelegateForFunctionPointer(dllGetClassObjectPtr, typeof(DllGetClassObject));

            // Call the DllGetClassObject to retrieve a class factory for out Filter class.
            Guid baseFilterGuid = filterPersistClass;
            Guid classFactoryGuid = typeof(IClassFactory).GUID;
            Object unk;


            if (dllGetClassObject(ref baseFilterGuid, ref classFactoryGuid, out unk) != 0)
                return null;

            return (unk as IClassFactory);
        }

        internal static IBaseFilter LoadFilterFromDll(string dllName, Guid interfaceId)
        {
            // Get a ClassFactory for our classID

            IClassFactory classFactory = GetClassFactory(dllName, interfaceId);
            if (classFactory == null) return null;

            try
            {
                // And create an IFilter instance using that class factory
                Guid baseFilterGuid = typeof(IBaseFilter).GUID;
                object obj;
                classFactory.CreateInstance(null, ref baseFilterGuid, out obj);
                return (obj as IBaseFilter);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

            return null;
        }

        internal static ushort HIWORD(IntPtr dwValue)
        {
            return (ushort)((((long)dwValue) >> 0x10) & 0xffff);
        }

        internal static ushort HIWORD(uint dwValue)
        {
            return (ushort)(dwValue >> 0x10);
        }

        internal static ushort LOWORD(IntPtr dwValue)
        {
            return (ushort)(((long)dwValue) & 0xffff);
        }

        internal static ushort LOWORD(uint dwValue)
        {
            return (ushort)dwValue;
        }

        internal static int GET_WHEEL_DELTA_WPARAM(IntPtr wParam)
        {
            return (short)HIWORD(wParam);
        }

        internal static int GET_WHEEL_DELTA_WPARAM(uint wParam)
        {
            return (short)HIWORD(wParam);
        }

        internal static int GET_X_LPARAM(IntPtr lParam)
        {
            return (short)LOWORD(lParam);
        }

        internal static int GET_X_LPARAM(uint lParam)
        {
            return (short)LOWORD(lParam);
        }

        internal static int GET_Y_LPARAM(IntPtr lParam)
        {
            return (short)HIWORD(lParam);
        }

        internal static int GET_Y_LPARAM(uint lParam)
        {
            return (short)HIWORD(lParam);
        }

        internal static int GET_KEYSTATE_WPARAM(IntPtr wParam)
        {
            return (short)LOWORD(wParam);
        }

        internal static int GET_KEYSTATE_WPARAM(uint wParam)
        {
            return (short)LOWORD(wParam);
        }

        internal static MouseButtons GetButtons(IntPtr wParam)
        {
            MouseButtons buttons = MouseButtons.None;
            int btns = GET_KEYSTATE_WPARAM(wParam);
            if ((btns & MK_LBUTTON) != 0) buttons |= MouseButtons.Left;
            if ((btns & MK_RBUTTON) != 0) buttons |= MouseButtons.Right;
            if ((btns & MK_MBUTTON) != 0) buttons |= MouseButtons.Middle;
            if ((btns & MK_XBUTTON1) != 0) buttons |= MouseButtons.XButton1;
            if ((btns & MK_XBUTTON2) != 0) buttons |= MouseButtons.XButton2;

            return buttons;
        }

        internal const int MK_LBUTTON = 0x0001;
        internal const int MK_RBUTTON = 0x0002;
        internal const int MK_MBUTTON = 0x0010;
        internal const int MK_XBUTTON1 = 0x0020;
        internal const int MK_XBUTTON2 = 0x0040;

        // --- SCREENSAVER

        [FlagsAttribute]
        public enum EXECUTION_STATE : uint
        {
            ES_SYSTEM_REQUIRED = 0x00000001,
            ES_DISPLAY_REQUIRED = 0x00000002,
            // Legacy flag, should not be used.
            // ES_USER_PRESENT   = 0x00000004,
            ES_AWAYMODE_REQUIRED = 0x00000040,
            ES_CONTINUOUS = 0x80000000,
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);

        public static void EnableScreensaver()
        {
            if (NativeMethods.SetThreadExecutionState(NativeMethods.EXECUTION_STATE.ES_CONTINUOUS | NativeMethods.EXECUTION_STATE.ES_DISPLAY_REQUIRED |
                NativeMethods.EXECUTION_STATE.ES_SYSTEM_REQUIRED | NativeMethods.EXECUTION_STATE.ES_AWAYMODE_REQUIRED) == 0) //Away mode for Windows >= Vista
            {
                NativeMethods.SetThreadExecutionState(NativeMethods.EXECUTION_STATE.ES_CONTINUOUS | NativeMethods.EXECUTION_STATE.ES_DISPLAY_REQUIRED |
                    NativeMethods.EXECUTION_STATE.ES_SYSTEM_REQUIRED); //Windows < Vista, forget away mode
            }
        }

        public static void DisableScreensaver()
        {
            NativeMethods.SetThreadExecutionState(NativeMethods.EXECUTION_STATE.ES_CONTINUOUS);
        }
    }
}
